<?php

namespace seecms\lib\provider;

use seecms\See;
use RuntimeException;

class Upload extends \SplFileInfo
{
    protected $file = null;

    /**
     * @var string 源文件名称
     */
    protected $original_name;

    /**
     * @var string 文件mime类型
     */
    protected $mime_type;

    /**
     * @var int 文件大小
     */
    protected $size;

    /**
     * 文件hash规则
     * @var array
     */
    protected $hash = [];

    /**
     * @var bool 判断是否 http post上传的
     */
    protected $is_http_post = false;

    /**
     * 从命名
     * @var bool
     */
    protected $rename = true;

    /**
     * @var string 移动文件错误
     */
    protected $error;

    /**
     * @throws RuntimeException
     */
    public function __construct($file = null)
    {
        if ($file === null) {
            $name = See::request()->post('name');

            $name = $name ?: 'file';

            $this->file = $_FILES[$name];
        } else {
            $this->file = $file;
        }

        if (empty($this->file['tmp_name'])) {
            throw new RuntimeException('The uploaded temporary file does not exist');
        }

        $this->error = $this->file['error'];
        if ($this->error) {
            throw new RuntimeException($this->getErrorMessage());
        }
        $this->original_name = $this->file['name'];
        $this->mime_type = $this->file['type'];
        $this->size = $this->file['size'];

        parent::__construct($this->file['tmp_name']);

        // 验证是否是http post请求
        if (!$this->isHttpPost()) {
            throw new RuntimeException($this->getErrorMessage());
        }

        $this->isValid();
    }


    /**
     * 判断文件是否是通过 HTTP POST 上传的
     * @return bool
     */
    public function isHttpPost(): bool
    {
        $is_ok = UPLOAD_ERR_OK === $this->error;

        return $this->is_http_post ? $is_ok : $is_ok && is_uploaded_file($this->getPathname());
    }

    public function isValid()
    {
        $setting = See::provider()->general->upload;

        $size = $setting['filesize'] ?? 0;
        if ($size && $this->getSize() > $size) {
            $this->error = 2;
            throw new RuntimeException($this->getErrorMessage());
        }

        $mime = $setting['mime'] ?? [];
        if (is_string($mime)) {
            $mime = json_decode($mime, true);
        }
        if ($mime && !in_array('file', $mime)) {
            if (in_array($this->getOriginalMime(), $mime)) {
                $this->error = -1;
                throw new RuntimeException($this->getErrorMessage());
            }
        }

        $extension = $setting['extension'] ?? '';
        if ($extension) {
            if (is_string($extension)) {
                $extension = explode(',', $extension);
            }
            if (!in_array($this->getExtension(), $extension)) {
                $this->error = -2;
                throw new RuntimeException($this->getErrorMessage());
            }
        }
    }

    /**
     * @param bool $rename
     * @return $this
     */
    public function rename(bool $rename = true): Upload
    {
        $this->rename = $rename;

        return $this;
    }

    /**
     * @param string|null $directory
     * @param string|null $filename
     * @return string
     */
    public function move(string $directory = null, string $filename = null): string
    {

        // 移动后临时文件将被删除
        $this->hash();
        $this->hash('md5');

        $config = See::config()->upload;

        if (empty($directory)) {
            if (preg_match('/^image/', $this->getOriginalMime())) {
                $directory = $config->directoryImage();
            } else {
                $directory = $config->directoryFile();
            }
        }

        $root = $config->root();
        $directory = $root . $directory;

        // 验证保存目录
        if (!is_dir($directory)) {
            if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
                throw new RuntimeException(sprintf('Unable to create the "%s" directory', $directory));
            }
        } elseif (!is_writable($directory)) {
            throw new RuntimeException(sprintf('Unable to write in the "%s" directory', $directory));
        }

        $filename = $this->getTargetFilename($filename);
        $target = rtrim($directory, '/\\') . '/' . $filename;
        set_error_handler(function ($type, $msg) use (&$error) {
            $error = $msg;
        });
        $moved = move_uploaded_file($this->getPathname(), $target);
        restore_error_handler();
        if (!$moved) {
            throw new RuntimeException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
        }

        @chmod($target, 0666 & ~umask());

        return str_replace($root, '', $target);
    }

    protected function getTargetFilename(string $filename = null): string
    {
        if (empty($filename)) {
            if ($this->rename) {
                $filename = md5(uniqid());
            } else {
                $filename = $this->original_name;
            }
        }

        $ext = pathinfo($filename, PATHINFO_EXTENSION);

        if (empty($ext)) {
            $filename .= '.' . $this->getExtension();
        }
        return $filename;
    }

    protected function getErrorMessage(): string
    {
        switch ($this->error) {
            case 1:
            case 2:
                $message = 'upload File size exceeds the maximum value';
                break;
            case 3:
                $message = 'only the portion of file is uploaded';
                break;
            case 4:
                $message = 'no file to uploaded';
                break;
            case 6:
                $message = 'upload temp dir not found';
                break;
            case 7:
                $message = 'file write error';
                break;
            case -1:
                $message = 'file mime not allowed';
                break;
            case -2:
                $message = 'file extension not allowed';
                break;
            default:
                $message = 'unknown upload error';
        }

        return $message;
    }

    public function getOriginalMime(): string
    {
        return $this->mime_type;
    }

    public function getOriginalName(): string
    {
        return $this->original_name;
    }

    /**
     * 获取文件扩展
     * @return string
     */
    public function getOriginalExtension(): string
    {
        return pathinfo($this->getOriginalName(), PATHINFO_EXTENSION);
    }

    /**
     * 获取文件扩展
     * @return string
     */
    public function getExtension(): string
    {
        return $this->getOriginalExtension();
    }

    /**
     * 获取文件的哈希散列值
     * @access public
     * @param string $type
     * @return string
     */
    public function hash(string $type = 'sha1'): string
    {
        if (!isset($this->hash[$type])) {
            $this->hash[$type] = hash_file($type, $this->getPathname());
        }

        return $this->hash[$type];
    }

    /**
     * 获取文件的MD5值
     * @access public
     * @return string
     */
    public function md5(): string
    {
        return $this->hash('md5');
    }

    /**
     * 获取文件的SHA1值
     * @access public
     * @return string
     */
    public function sha1(): string
    {
        return $this->hash();
    }

    public function getSize()
    {
        return $this->size;
    }
}